/**
* Copyright (c) 2022 - present TinyVue Authors.
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/

import { formatDate } from '@opentiny/vue-renderless/common/deps/date-util'
import { getFirstDayOfMonth, getDayCountOfMonth, getWeekNumber, prevDate, nextDate, isDate, clearTime } from '@opentiny/vue-renderless/common/deps/date-util'
import { DATEPICKER } from '@opentiny/vue-renderless/common'

const formatJudg = ({ day, offset, j, i, cell, count, dateCountOfLastMonth }) => {
  const nodfpm = day + offset < 0 ? 7 + day + offset : day + offset

  if (j + i * 7 >= nodfpm) {
    cell.text = count++
  } else {
    cell.text = dateCountOfLastMonth - (nodfpm - (j % 7)) + 1 + i * 7
    cell.type = DATEPICKER.PreMonth
  }

  return { count }
}

export const getOffsetDay = (props) => () => {
  const week = props.firstDayOfWeek
  return week > 3 ? 7 - week : -week
}

export const getWeeks = ({ props, state }) => () => {
  const week = props.firstDayOfWeek
  return state.constWeeks.concat(state.constWeeks).slice(week, week + 7)
}

export const getDateTimestamp = (time) => {
  if (typeof time === 'number' || typeof time === 'string') {
    return clearTime(new Date(time)).getTime()
  } else if (time instanceof Date) {
    return clearTime(time).getTime()
  }

  return NaN
}

export const arrayFindIndex = (arr, pred) => {
  for (let i = 0, len = arr.length; i !== len; ++i) {
    if (pred(arr[i])) {
      return i
    }
  }

  return -1
}

export const arrayFind = (arr, pred) => {
  const idx = arrayFindIndex(arr, pred)
  return ~idx ? arr[idx] : undefined
}

const getSelected = ({ props, cell, format, t, cellDate, selectedDate }) => {
  let selected = cell.selected

  if (props.selectionMode === 'dates') {
    selected = arrayFind(selectedDate, (date) => formatDate(date, format, t) === formatDate(cellDate, format, t))
  } else {
    selected = arrayFind(selectedDate, (date) => date.getTime() === cellDate.getTime())
  }

  return selected
}

export const getCell = ({ state, props }) => (row, i, j) => {
  const now = getDateTimestamp(new Date())
  let cell = row[props.showWeekNumber ? j + 1 : j]

  if (!cell) {
    cell = {
      row: i,
      column: j,
      inRange: false,
      start: false,
      end: false,
      type: DATEPICKER.Normal
    }
  }

  cell.type = DATEPICKER.Normal

  const index = i * 7 + j
  const time = nextDate(state.startDate, index - state.offsetDay).getTime()

  cell.inRange = time >= getDateTimestamp(props.minDate) && time <= getDateTimestamp(props.maxDate)
  cell.start = props.minDate && time === getDateTimestamp(props.minDate)
  cell.end = props.maxDate && time === getDateTimestamp(props.maxDate)

  // isToday
  time === now && (cell.type = DATEPICKER.Today)

  return { cell, cellDate: new Date(time) }
}

const doCount = ({ i, day, offset, j, cell, count, dateCountOfLastMonth, dateCountOfMonth }) => {
  if (i >= 0 && i <= 1) {
    const ret = formatJudg({
      day,
      offset,
      j,
      i,
      cell,
      count,
      dateCountOfLastMonth
    })
    count = ret.count
  } else {
    if (count <= dateCountOfMonth) {
      cell.text = count++
    } else {
      cell.text = count++ - dateCountOfMonth
      cell.type = DATEPICKER.NextMonth
    }
  }

  return count
}

export const coerceTruthyValueToArray = (val) => (Array.isArray(val) ? val : val ? [val] : [])

export const getRows = ({ api, props, state, t, vm }) => () => {
  const date = new Date(state.year, state.month, 1)
  let day = getFirstDayOfMonth(date)
  const dateCountOfMonth = getDayCountOfMonth(date.getFullYear(), date.getMonth())
  const dateCountOfLastMonth = getDayCountOfMonth(date.getFullYear(), date.getMonth() === 0 ? 11 : date.getMonth() - 1)

  day = day === 0 ? 7 : day

  const offset = state.offsetDay
  const rows = state.tableRows
  const startDate = state.startDate
  const disabledDate = props.disabledDate
  const cellClassName = props.cellClassName
  const selectedDate = props.selectionMode === DATEPICKER.Dates ? coerceTruthyValueToArray(props.value) : []
  let count = 1

  for (let i = 0; i < 6; i++) {
    const row = rows[i]

    if (props.showWeekNumber && !row[0]) {
      row[0] = {
        type: DATEPICKER.Week,
        text: getWeekNumber(nextDate(startDate, i * 7 + 1))
      }
    }

    for (let j = 0; j < 7; j++) {
      const { cell, cellDate } = api.getCell(row, i, j, DATEPICKER.Normal, props)

      count = doCount({ i, day, offset, j, cell, count, dateCountOfLastMonth, dateCountOfMonth })

      cell.disabled = typeof disabledDate === 'function' && disabledDate(cellDate)
      cell.selected = getSelected({ props, cell, api, format: DATEPICKER.DateFormats.date, t, cellDate, selectedDate })
      cell.customClass = typeof cellClassName === 'function' && cellClassName(cellDate)

      vm.$set(row, props.showWeekNumber ? j + 1 : j, cell)
    }

    if (props.selectionMode === DATEPICKER.Week) {
      const [start, end] = props.showWeekNumber ? [1, 7] : [0, 6]
      const isWeekActive = api.isWeekActive(row[start + 1])

      Object.assign(row[start], { inRange: isWeekActive, start: isWeekActive })
      Object.assign(row[end], { inRange: isWeekActive, end: isWeekActive })
    }
  }

  return rows
}

export const watchMinDate = ({ api, props }) => (value, oldvalue) => {
  if (getDateTimestamp(value) !== getDateTimestamp(oldvalue)) {
    api.markRange(props.minDate, props.maxDate)
  }
}

export const watchMaxDate = ({ api, props }) => (value, oldvalue) => {
  if (getDateTimestamp(value) !== getDateTimestamp(oldvalue)) {
    api.markRange(props.minDate, props.maxDate)
  }
}

export const cellMatchesDate = (state) => (cell, date) => {
  const value = new Date(date)

  return state.year === value.getFullYear() && state.month === value.getMonth() && Number(cell.text) === value.getDate()
}

export const getCellRangeClasses = ({ props }) => (cell, classes) => {
  if (cell.type === DATEPICKER.Normal || cell.type === DATEPICKER.Today || props.selectionMode === DATEPICKER.Week) {
    classes.push(DATEPICKER.InRange)

    if (cell.start) {
      classes.push(DATEPICKER.StartDate)
    }

    if (cell.end) {
      classes.push(DATEPICKER.EndDate)
    }
  }
}

export const getCellClasses = ({ api, props, state }) => (cell) => {
  let classes = []
  const selectionMode = props.selectionMode

  const defaultValue = props.defaultValue ? (Array.isArray(props.defaultValue) ? props.defaultValue : [props.defaultValue]) : []

  if ((cell.type === DATEPICKER.Normal || cell.type === DATEPICKER.Today) && !cell.disabled) {
    classes.push(DATEPICKER.Aviailable)

    if (cell.type === DATEPICKER.Today) {
      classes.push(DATEPICKER.Today)
    }
  } else {
    classes.push(cell.type)
  }

  if (cell.type === DATEPICKER.Normal && defaultValue.some((date) => api.cellMatchesDate(cell, date))) {
    classes.push(DATEPICKER.Default)
  }

  if (selectionMode === DATEPICKER.Day && ~[DATEPICKER.Normal, DATEPICKER.Today].indexOf(cell.type) && api.cellMatchesDate(cell, props.value || state.date)) {
    classes.push(DATEPICKER.Current)
  }

  if (cell.inRange) {
    api.getCellRangeClasses(cell, classes)
  }

  if (cell.disabled) {
    classes.push(DATEPICKER.Disabled)
  }

  if (cell.selected) {
    classes.push(DATEPICKER.Selected)
  }

  if (cell.customClass) {
    classes.push(cell.customClass)
  }

  return classes.join(' ')
}

export const getDateOfCell = ({ props, state }) => (row, column) => {
  const offsetFromStart = row * 7 + (column - (props.showWeekNumber ? 1 : 0)) - state.offsetDay

  return nextDate(state.startDate, offsetFromStart)
}

export const isWeekActive = ({ props, state }) => (cell) => {
  if (props.selectionMode !== DATEPICKER.Week) {
    return false
  }

  const newDate = new Date(state.year, state.month, 1)
  const year = newDate.getFullYear()
  const month = newDate.getMonth()

  if (cell.type === DATEPICKER.PreMonth) {
    newDate.setMonth(month === 0 ? 11 : month - 1)
    newDate.setFullYear(month === 0 ? year - 1 : year)
  }

  if (cell.type === DATEPICKER.NextMonth) {
    newDate.setMonth(month === 11 ? 0 : month + 1)
    newDate.setFullYear(month === 11 ? year + 1 : year)
  }

  newDate.setDate(parseInt(cell.text, 10))

  if (isDate(props.value)) {
    const dayOffset = ((props.value.getDay() - props.firstDayOfWeek + 7) % 7) - 1
    const weekDate = prevDate(props.value, dayOffset)

    return weekDate.getTime() === newDate.getTime()
  }

  return false
}

export const markRange = ({ props, state }) => (minDate, maxDate) => {
  const minDateTimestamp = getDateTimestamp(minDate)
  const maxDateTimestamp = getDateTimestamp(maxDate) || minDateTimestamp

  minDate = Math.min(minDateTimestamp, maxDateTimestamp)
  maxDate = Math.max(minDateTimestamp, maxDateTimestamp)

  const startDate = state.startDate
  const rows = state.rows

  for (let i = 0, k = rows.length; i < k; i++) {
    const row = rows[i]

    for (let j = 0, l = row.length; j < l; j++) {
      if (!props.showWeekNumber || j !== 0) {
        const cell = row[j]
        const index = i * 7 + j + (props.showWeekNumber ? -1 : 0)
        const time = nextDate(startDate, index - state.offsetDay).getTime()

        cell.inRange = minDate && time >= minDate && time <= maxDate
        cell.start = minDate && time === minDate
        cell.end = maxDate && time === maxDate
      }
    }
  }
}

export const handleMouseMove = ({ api, emit, props, state }) => (event) => {
  if (!props.rangeState.selecting) {
    return
  }

  let targetEl = event.target

  if (targetEl.tagName === 'SPAN') {
    targetEl = targetEl.parentNode.parentNode
  }

  if (targetEl.tagName === 'DIV') {
    targetEl = targetEl.parentNode
  }

  if (targetEl.tagName !== 'TD') {
    return
  }

  const row = targetEl.parentNode.rowIndex - 1
  const column = targetEl.cellIndex

  if (state.rows[row][column].disabled) {
    return
  }

  if (row !== state.lastRow || column !== state.lastColumn) {
    state.lastRow = row
    state.lastColumn = column

    emit('changerange', {
      minDate: props.minDate,
      maxDate: props.maxDate,
      rangeState: {
        selecting: true,
        endDate: api.getDateOfCell(row, column)
      }
    })
  }
}

const getTarget = (event) => {
  let target = event.target

  if (target.tagName === 'SPAN') {
    target = target.parentNode.parentNode
  }

  if (target.tagName === 'DIV') {
    target = target.parentNode
  }

  return target
}

export const removeFromArray = (arr, pred) => {
  const idx = typeof pred === 'function' ? arrayFindIndex(arr, pred) : arr.indexOf(pred)

  return idx >= 0 ? [...arr.slice(0, idx), ...arr.slice(idx + 1)] : arr
}

export const handleClick = ({ api, emit, props, state }) => (event) => {
  let target = getTarget(event)

  if (target.tagName !== 'TD') {
    return
  }

  const row = target.parentNode.rowIndex - 1
  const column = props.selectionMode === DATEPICKER.Week ? 1 : target.cellIndex
  const cell = state.rows[row][column]

  if (cell.disabled || cell.type === DATEPICKER.Week) {
    return
  }

  const newDate = api.getDateOfCell(row, column)

  if (props.selectionMode === DATEPICKER.Range) {
    if (!props.rangeState.selecting) {
      emit('pick', { minDate: newDate, maxDate: null })
      props.rangeState.selecting = true
    } else {
      if (newDate >= props.minDate) {
        emit('pick', { minDate: props.minDate, maxDate: newDate })
      } else {
        emit('pick', { minDate: newDate, maxDate: props.minDate })
      }

      props.rangeState.selecting = false
    }
  } else if (props.selectionMode === DATEPICKER.Day) {
    emit('pick', newDate)
  } else if (props.selectionMode === DATEPICKER.Week) {
    const weekNumber = getWeekNumber(newDate)
    const value = newDate.getFullYear() + 'w' + weekNumber

    emit('pick', {
      year: newDate.getFullYear(),
      week: weekNumber,
      value,
      date: newDate
    })
  } else if (props.selectionMode === DATEPICKER.Dates) {
    const value = props.value || []

    const newValue = cell.selected ? removeFromArray(value, (date) => date.getTime() === newDate.getTime()) : [...value, newDate]

    emit('pick', newValue)
  }
}
